package com.mall.framework.security;

import com.mall.common.constant.CacheConstants;
import com.mall.common.model.UserAuth;
import com.mall.framework.service.CacheService;
import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.SignatureException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    @Value("${jwt.secret:mall-secret-key}")
    private String jwtSecret;

    @Value("${jwt.expiration:86400000}")
    private long jwtExpiration;

    private final CacheService cacheService;

    public String generateToken(Authentication authentication) {
        //1.获取用户名
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        UserAuth userAuth = (UserAuth) userDetails;
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userAuth.getUserId());
        claims.put("username", userDetails.getUsername());
        
        String token = Jwts.builder()
            .setClaims(claims)
            .setSubject(userDetails.getUsername())
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
            
        // 将Token存储到Redis
        long expireTimeInSeconds = jwtExpiration / 1000;
        cacheService.storeUserToken(userAuth.getUserId(), token, expireTimeInSeconds);
        cacheService.storeUserInfo(userAuth.getUserId(), userAuth, CacheConstants.USER_INFO_EXPIRE_TIME);
        
        log.info("JWT Token已生成并存储到Redis，用户ID: {}, 过期时间: {}秒", userAuth.getUserId(), expireTimeInSeconds);
        
        return token;
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();

        return claims.getSubject();
    }

    public Long getUserIdFromToken(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        return claims.get("userId", Long.class);
    }

    public boolean validateToken(String token) {
        try {
            // 首先检查Token是否在黑名单中
            if (cacheService.isTokenInBlacklist(token)) {
                log.warn("Token在黑名单中: {}", token);
                return false;
            }
            
            // 验证JWT签名和过期时间
            Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
            
            // 获取用户ID并检查Redis中的Token是否匹配
            Long userId = claims.get("userId", Long.class);
            String storedToken = cacheService.getUserToken(userId);
            
            if (storedToken == null || !storedToken.equals(token)) {
                log.warn("Redis中的Token不匹配或不存在，用户ID: {}", userId);
                return false;
            }
            
            return true;
        } catch (MalformedJwtException ex) {
            log.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            log.info("Expired JWT token"); // 降级为INFO
        } catch (UnsupportedJwtException ex) {
            log.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            log.error("JWT claims string is empty");
        } catch (SecurityException ex) { // 兼容签名异常
            log.error("Invalid JWT signature");
        }
        return false;
    }
    
    /**
     * 刷新Token
     * @param token 当前Token
     * @return 新的Token
     */
    public String refreshToken(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
            Long userId = claims.get("userId", Long.class);
            String username = claims.getSubject();
            
            // 生成新Token
            Date now = new Date();
            Date expiryDate = new Date(now.getTime() + jwtExpiration);
            Map<String, Object> newClaims = new HashMap<>();
            newClaims.put("userId", userId);
            newClaims.put("username", username);
            
            String newToken = Jwts.builder()
                .setClaims(newClaims)
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
            
            // 将旧Token加入黑名单
            long expireTimeInSeconds = jwtExpiration / 1000;
            cacheService.addTokenToBlacklist(token, CacheConstants.BLACKLIST_EXPIRE_TIME);
            
            // 存储新Token
            cacheService.storeUserToken(userId, newToken, expireTimeInSeconds);
            
            log.info("Token已刷新，用户ID: {}, 新过期时间: {}秒", userId, expireTimeInSeconds);
            
            return newToken;
        } catch (Exception e) {
            log.error("刷新Token失败", e);
            throw new RuntimeException("刷新Token失败", e);
        }
    }
    
    /**
     * 使Token失效
     * @param token JWT Token
     */
    public void invalidateToken(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
            Long userId = claims.get("userId", Long.class);
            
            // 将Token加入黑名单
            cacheService.addTokenToBlacklist(token, CacheConstants.BLACKLIST_EXPIRE_TIME);
            
            // 删除用户Token
            cacheService.removeUserToken(userId);
            
            // 删除用户信息
            cacheService.removeUserInfo(userId);
            
            log.info("Token已失效，用户ID: {}", userId);
        } catch (Exception e) {
            log.error("使Token失效失败", e);
        }
    }
} 